﻿using System;
using System.Linq;

namespace MLSharp.Filters
{
	/// <summary>
	/// Provides a simple mechanism for temporarily removing
	/// attributes from a dataset, running a classifier (or clusterer),
	/// then restoring the attribute.
	/// </summary>
	public class PassThroughAttributeRemover
	{
		#region Private Fields

		/// <summary>
		/// The index of the attribute to remove.
		/// </summary>
		private int[] mAttributeIndices;

		#endregion

		#region Public Constructors

		/// <summary>
		/// The indices of attributes to remove.
		/// </summary>
		/// <param name="attributeIndices"></param>
		public PassThroughAttributeRemover(params int[] attributeIndices)
		{
			mAttributeIndices = attributeIndices;
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Filters the specified dataset and passes it to the specified action.
		/// </summary>
		/// <param name="dataSet">The dataset to filter.</param>
		/// <param name="action">The action to perform.</param>
		public void Filter(IDataSet dataSet, Action<IDataSet> action)
		{
			//Sort the data to remove
			Array.Sort(mAttributeIndices);
			
			//Store a reference to the attributes we're going to remove; we'll need
			//them later.
			DataAttribute[] removedAttributes = new DataAttribute[mAttributeIndices.Length];

			for (int i=0; i < mAttributeIndices.Length; i++)
			{
				removedAttributes[i] = dataSet.Attributes[mAttributeIndices[i]];
			}

			//Backup all the instance values.
			double[][] values = new double[dataSet.Instances.Count][];

			for (int i=0; i < dataSet.Instances.Count; i++)
			{
				values[i] = new double[mAttributeIndices.Length];

				for (int j = 0; j < mAttributeIndices.Length; j++)
				{
					values[i][j] = dataSet.Instances[i].Values[mAttributeIndices[j]];
				}
			}

			//Now that we have the data backed up, go ahead and remove the attributes
			for (int i = 0; i < mAttributeIndices.Length; i++)
			{
				dataSet.Attributes.Remove(removedAttributes[i]);
			}

			try
			{
				action(dataSet);
			}
			//Restore the dataset here, that way if an exception is thrown by the action,
			//the original dataset is still in a consistent state.
			finally
			{
				//Add the attribute back
				for (int i = 0; i < mAttributeIndices.Length; i++)
				{
					dataSet.Attributes.Insert(mAttributeIndices[i], removedAttributes[i]);
				}

				//And restore the values
				for (int i = 0; i < dataSet.Instances.Count; i++)
				{
					for (int j = 0; j < mAttributeIndices.Length; j++)
					{
						dataSet.Instances[i].Values[mAttributeIndices[j]] = values[i][j];
					}
				}
			}
		}

		#endregion
	}
}
